

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;


public class Ant implements defAnt {

	
	
	/*
	 * Fields
	 */
	
	private static int borderPheromone = 40;
	private static int rangeAngle = 5;
	
	/** Futterwert einer Ameise ohne Futter */
	public static  int NOFOOD = 17;
	
	/** Futterwert einer Ameise mit Futter */
	public static  int GOTFOOD = 30;

	/** moegliche Richtungen, die die Ameise gehen kann */
	public static enum DIRECTION { leftBottomLeft, left, leftTopLeft, leftTop, leftTopTop, top, rightTopTop, rightTop, rightTopRight, right, rightBottomRight, rightBottom, rightBottomBottom,
		bottom, leftBottomBottom, leftBottom };//Reihenfolge wichtig!!!!
	
	/** Array, das die Standardreihenfolge angeibt, in der die Ameise nach ihrem Weg sucht */
	public static final DIRECTION [] defaultPrefDir = { DIRECTION.leftBottomLeft, DIRECTION.left, DIRECTION.leftTopLeft, DIRECTION.leftTop, DIRECTION.leftTopTop, DIRECTION.top, DIRECTION.rightTopTop, DIRECTION.rightTop, DIRECTION.rightTopRight, DIRECTION.right, DIRECTION.rightBottomRight, DIRECTION.rightBottom, DIRECTION.rightBottomBottom,
		DIRECTION.bottom, DIRECTION.leftBottomBottom, DIRECTION.leftBottom };
	
	/** die zu jeder Richtung gehoerenden Aenderungen der x- bzw. der y-Richtung
	 * 	1.Stelle: x-Coordinate, 1->-1, 2->0, 3->1
	 	2.Stelle: y-Coordinate, 1->-1, 2->0, 3->1
	 */
	private static final int [] dirActions = { 13, 12, 12, 11, 11, 21, 21, 31, 31, 32, 32, 33, 33, 23, 23, 13 };
		
	/** liefert die Zufallszahlen fuer alle Ameisen */
	private static Random rand = new Random();
	
	/** gibt an, wie gross die Tore des Ameisenhuegels sind,
	 * die die aus verschiedenen Richtungen kommenden
	 * Ameisen benutzen koennen
	 */
	private static  int exactMod=3;
	
	/** Zeitschritte, die die Ameise verharrt, falls ihr Weg
	 * von einer anderen versperrt ist, bis sie weiterlaeuft 
	 */
	private static  int waitConstant = 4;
	
	/** Anzahl der Schritte in die gleiche Richtung, bis die 
	 * Ameise fuer 10 nicht nach einer Pheromonspur sucht
	 */
	private static  int queuedCounterBorder = 4;
	
	/**
	 * Anzahl Zeitschritte, nach denen die Aenderung der
	 * Ameisenposition ueberprueft wird
	 */
	private static int gettingOnTimeStep = 10;
	
	/**
	 * Differenz, die die Ameise sowohl in x- als auch in y-Richtung
	 * vorangekommen sein muss
	 */
	private static int gettingOnDiff = 6;
	
	/**
	 * Zeit, nach der die Richtung zum Ameisenhuegel aktualisiert wird
	 */
	private static int actualizingHomeTimeStep = 100;
	
	/**
	 * Anzahl Zeitschritte, nach denen wieder nach einer zu folgenden
	 * Duftspur gesucht wird
	 */
	private static int followingTimeStep = 2;
	
	
	
	/*
	 * Attributes
	 */
	
	/** aktuelle Position der Ameise */
	private Point position;
	
	/** die Pheromonespur der Ameise */
	private PTrail pTrail;

	/** Referenz auf die AntSimulation der Ameise */
	private AntSimulation antSim;
	
	/** aktuell getragenes Futter der Ameise */
	public int food = NOFOOD;
	
	/** Richtungen, in der Reihenfolge der Abarbeitung */
	private DIRECTION [] prefDir;
	
	/** fuer zusammengesetzte Richtungen, die nicht direkt an das Pixel(position) angrenzen */
	private boolean secondDirectionChosen = false;
	
	/** zaehlt, wann gewisse Aktionen von der Ameise ausgefuehrt werden */
	private int counter;
		
	/** merkt sich die vorletzte Richtung damit keine schleife entsteht */
	private DIRECTION beforeLastDir;
	
	/** zaehlt hoch; beeinflusst die Richtung der Ameise, wenn ein Zusammenstoss mit einer anderen vorliegt */
	private int angleWay = 0;
	
	/** zaehlt, wie haeufig die Ameise keinen Schritt gemacht hat */
	private int waiter = 0;
	
	/** alle 10 Zeitschritte aktualisiert; zur Feststellung, ob sich die Ameise effektiv fortbewegt hat */
	private Point equalPosition = new Point(0,0);
	
	/** gibt an, ob die Ameise einer Duftspur folgen kann..... heisst wenn sie kein futter traegt zum beispiel */
	private boolean following = true;
	
	/** merkt sich die Richtung, in die die Ameise laeuft */
	private DIRECTION queuedDir;
	
	/** merkt sich die Anzahl der Schritte, die die Ameise nacheinander in dieselbe Richtung macht */
	private int queuedCounter = 0;
	
	
	
	
	/*
	 * Constructors	
	 */
	
	/** Standardkonstruktor
	 * 
	 *  @param p Punkt, auf dem die Ameise erzeugt wird
	 *  @param aS AntSimulation der Ameise 
	 *  @link AntSimulation
	 */
	private Ant( Point p ,AntSimulation aS){
		antSim=aS;		
		position = new Point( p );
		prefDir = defaultPrefDir.clone();
		antSim.getForestground()[p.x][p.y] *= -1;
		counter=0;
		exactMod = antSim.anthill_rectangle.size/exactMod;
		queuedDir = prefDir[0];
		pTrail = new PheromoneTrail(antSim);
	}
	
	/**
	 * Konstruktor, weist Ameise zufaelligen Richtungsvektor zu und setzt
	 * die uebergebene Richtung an den Anfang
	 * @param p Startpunkt der Ameise
	 * @param dir Anfangsrichtung der Ameise
	 * @param antSim {@link AntSimulation} 
	 */
	public Ant( Point p, DIRECTION dir ,AntSimulation antSim ){
		this( p, antSim );
		for(int i=0; i<16; i++){
			changeOrder(defaultPrefDir[rand.nextInt(defaultPrefDir.length)]);
		}
		changeOrder( dir );	
	}
	
	
	
	
	/*
	 * Methods
	 */
	
	//static Methods
	public static void setExactMode(int value){
		exactMod=value;
	}
	
	public static void setQueuedCounterBorder(int value){
		queuedCounterBorder=value;
	}
	
	public static void setWaitConstant(int value){
		waitConstant=value;
	}
	
	public static void setAngle(int value){
		rangeAngle=value;
	}
	
	public static void setBorderPheromones(int value){
		borderPheromone=value;
	}
	
		
	//getter - Methods
	
	public static void setGettingOnTimeStep(int gettingOnTimeStep) {
		Ant.gettingOnTimeStep = gettingOnTimeStep;
	}

	public static void setGettingOnDiff(int gettingOnDiff) {
		Ant.gettingOnDiff = gettingOnDiff;
	}

	public static void setActualizingHomeTimeStep(int actualizingHomeTimeStep) {
		Ant.actualizingHomeTimeStep = actualizingHomeTimeStep;
	}

	public static void setFollowingTimeStep(int followingTimeStep) {
		Ant.followingTimeStep = followingTimeStep;
	}

	public static int getBorderPheromone() {
		return borderPheromone;
	}

	public static int getGettingOnTimeStep() {
		return gettingOnTimeStep;
	}

	public static int getGettingOnDiff() {
		return gettingOnDiff;
	}

	public static int getActualizingHomeTimeStep() {
		return actualizingHomeTimeStep;
	}

	public static int getFollowingTimeStep() {
		return followingTimeStep;
	}

	public static int getRangeAngle() {
		return rangeAngle;
	}


	public static int getExactMod() {
		return exactMod;
	}

	public static int getWaitConstant() {
		return waitConstant;
	}

	public static int getQueuedCounterBorder() {
		return queuedCounterBorder;
	}
	
	
	

	/**
	 * x - Koordinate der Ameise
	 * @return int
	 */
	public int getX(){
		return this.position.x;
	}
	
	/**
	 * y - Koordinate der Ameise
	 * @return int
	 */
	public int getY(){
		return this.position.y;
	}
	
	/**
	 * Futterwert der Ameise
	 * @return int
	 */
	public int getFood(){
		return this.food;
	}
	
	/**
	 * Pheromonspur der Ameise
	 * @return PTrail
	 */
	public PTrail getPtrail(){
		return (PheromoneTrail) this.pTrail;
	}
	
	
	// other Methods

	/**
	 *  setzt die uebergebene Richtung an den Anfang des Richtungsvektors der Ameise
	 *  @param dir neue Favoritenrichtung
	 */
	private void changeOrder( DIRECTION dir ){
		DIRECTION saveDir = dir, tmpDir = prefDir[0];
		int i = 0;
		while( prefDir[i] != dir ){
			tmpDir = prefDir[i];
			prefDir[i] = saveDir;
			saveDir = tmpDir;
			i++;
		}
		prefDir[i] = tmpDir;
	}

	/**
	 * setzt die uebergebene Richtung an das Ende des Richtungsvektors der Ameise
	 * @param dir nach hinten; vermindert die Wahrscheinlichkeit fuer das erneute Laufen in die Richtung
	 */
	private void sendToBack( DIRECTION dir ){
		DIRECTION saveDir, tmpDir = dir;
		int i = prefDir.length-1;
		while( prefDir[i] != dir ){
			saveDir = prefDir[i];
			prefDir[i] = tmpDir;
			tmpDir = saveDir;
			i--;
		}
		prefDir[i] = tmpDir;
	}
	
	/**
	 * sucht die direkte Richtung zum angegebenen Punkt, z.B. Ameisenhaufen; haengt von der aktuellen Position ab
	 * @param dest gibt Ziel der Ameise an
	 */
	private void goHome(Point dest){
		int xdiff = dest.x - position.x;
		int ydiff = dest.y - position.y;
		double quot = Math.abs((double)ydiff / (double)xdiff); //Steigung
		
		//x==0
		if(Math.abs(xdiff)<exactMod){
			if(ydiff<0)
				changeOrder(DIRECTION.top);
			else
				changeOrder(DIRECTION.bottom);
		//x>0
		} else if(xdiff>0){
			if(Math.abs(ydiff)<exactMod){
				changeOrder(DIRECTION.right);
			} else if(ydiff>0){
				if(quot>1.25)
					changeOrder(DIRECTION.rightBottomBottom);
				else if(quot<0.75)
					changeOrder(DIRECTION.rightBottomRight);
				else
					changeOrder(DIRECTION.rightBottom);
			} else if(ydiff<0){
				if(quot>1.25)
					changeOrder(DIRECTION.rightTopTop);
				else if(quot<0.75)
					changeOrder(DIRECTION.rightTopRight);
				else
					changeOrder(DIRECTION.rightTop);
			} 
		//x<0
		} else if(xdiff<0){ 
			if(Math.abs(ydiff)<exactMod){
				changeOrder(DIRECTION.left);
			} else if(ydiff>0){
				if(quot>1.25)
					changeOrder(DIRECTION.leftBottomBottom);
				else if(quot<0.75)
					changeOrder(DIRECTION.leftBottomLeft);
				else
					changeOrder(DIRECTION.leftBottom);
			} else if(ydiff<0){
				if(quot>1.25)
					changeOrder(DIRECTION.leftTopTop);
				else if(quot<0.75)
					changeOrder(DIRECTION.leftTopLeft);
				else
					changeOrder(DIRECTION.leftTop);
			}
		}
	}//goHome()
	
	/**
	 * sucht die naechste Richtung, in die die Ameise gehen kann
	 * @param nextPoint Referenz, auf die der naechste Schritt gespeichert wird
	 * @return gibt false zurueck, falls kein Schritt moeglich, sonst true
	 */
	private boolean stepDirection( Point nextPoint ){
		
		counter++;
		if( counter%actualizingHomeTimeStep==0 && this.food==GOTFOOD ){
			goHome( antSim.anthill_rectangle.getMidPoint() );
			counter=0;
			secondDirectionChosen=false;
		}
		
		if( queuedCounter > queuedCounterBorder && following ){
			counter = 0;
			following = false;
		}
		
		if( counter%gettingOnTimeStep==0 ){
			if( following ){
				if( Math.abs(position.x-equalPosition.x) < gettingOnDiff && Math.abs(position.y-equalPosition.y) < gettingOnDiff ){
					following = false;
				}
			} else if (this.food==NOFOOD)
				following = true;
			equalPosition = new Point( position );
		}
		
		if( following && counter%followingTimeStep==0 && this.food == NOFOOD ){
			int aktPheromone = antSim.getForestground()[position.x][position.y];
			
			borderPheromone = Math.max( borderPheromone, aktPheromone );
			int maxPheromone = borderPheromone;
			
			DIRECTION actDirection = prefDir[0]; 
			for( int i = rangeAngle-1; i >= 0; i-- ){
				int action = Ant.dirActions[ (actDirection.ordinal() + i )%16 ];
				int akty = position.y + action%10 - 2;//xdiff;
				action/=10;
				int aktx = position.x + action - 2;//ydiff;
				if( aktx >= 0 && aktx < antSim.getSimWidth() && akty >= 0 
						&& akty < antSim.getSimHeight() 
						&& antSim.getForestground()[aktx][akty] > borderPheromone ){
					changeOrder( Ant.defaultPrefDir[ (actDirection.ordinal() + i)%16 ] );
					if( antSim.getForestground()[aktx][akty] > maxPheromone )
						maxPheromone = antSim.getForestground()[aktx][akty];
					else
						changeOrder( prefDir[1] );
					secondDirectionChosen = false;
//					break; //i runter oder hochzaehlen
				}
				action = Ant.dirActions[ (actDirection.ordinal() - i + 16)%16 ];
				akty = position.y + action%10 - 2;//xdiff;
				action/=10;
				aktx = position.x + action - 2;//ydiff;
				if( aktx >= 0 && aktx < antSim.getSimWidth() && akty >= 0 
						&& akty < antSim.getSimHeight() 
						&& antSim.getForestground()[aktx][akty] > borderPheromone ){
					changeOrder( Ant.defaultPrefDir[ (actDirection.ordinal() - i + 16)%16 ] );
					if( antSim.getForestground()[aktx][akty] > maxPheromone )
						maxPheromone = antSim.getForestground()[aktx][akty];
					else
						changeOrder( prefDir[1] );
					secondDirectionChosen = false;
//					break; //i runter oder hochzaehlen
				}
			}
		}
		
		
		for( int i = 0; i < prefDir.length; i++ ){
	
			DIRECTION secDir = prefDir[1];
			if( i > 1 )
				secondDirectionChosen = false;
			DIRECTION actDir = prefDir[i];
			
			if( i > 0 && actDir == beforeLastDir )
				continue;
			if( actDir == Ant.defaultPrefDir[ (prefDir[0].ordinal() + 8)%16 ]){
				actDir = Ant.defaultPrefDir[ (prefDir[0].ordinal() + (int)Math.pow(-1, rand.nextInt(1))*angleWay++ + 16 )%16 ];
				sendToBack(prefDir[0]);
			}
			
			int action = Ant.dirActions[ actDir.ordinal() ];
			if( actDir.ordinal()%2 == 0 ){
				secondDirectionChosen = true;
				secDir = Ant.defaultPrefDir[ actDir.ordinal() + 1 ];
			}
			
			int akty = nextPoint.y + action%10 - 2;//xdiff;
			action/=10;
			int aktx = nextPoint.x + action - 2;//ydiff;
			
			if( aktx >= 0 && aktx < antSim.getSimWidth() && akty >= 0 
					&& akty < antSim.getSimHeight() ){
				if( antSim.getForestground()[aktx][akty] < -255 && this.food == NOFOOD ){
					this.food = GOTFOOD;
					antSim.getForestground()[aktx][akty] += GOTFOOD;
					following = false;
					if( antSim.getForestground()[aktx][akty] >= -255 )
						antSim.getForestground()[aktx][akty] = 1;
					return false;
				} else if( antSim.getForestground()[aktx][akty] > 0 ){
					nextPoint.x = aktx;
					nextPoint.y = akty;
					if( i > 0 )
						beforeLastDir = prefDir[0];
					changeOrder( prefDir[i] );
					if( secondDirectionChosen ){
						changeOrder( secDir );
					}
					if( following ){
						if(queuedDir == actDir )
							queuedCounter++;
						else{
							queuedDir = actDir;
							queuedCounter = 0;
						}
					}
					//nicht mehr gewartet!!
					waiter = 0;
					return true;
				} else if( antSim.getForestground()[aktx][akty] < 0 && antSim.getForestground()[aktx][akty] > -250 ){
					waiter++;
					secondDirectionChosen = false;
					//beim Ueberschreiten der Wartekonstante wurde nicht mehr aktiv gewartet --> waitcounter beim naechsten Schritt auf null
					if( waiter <= waitConstant ){
						return false;
					}
				}
			}
			
			//wenn die Ameisen am Huegel angekommen sind, geben sie das Futter ab
			if(antSim.anthill_rectangle.isAtBorder(aktx, akty) ){
				following = true;
				this.food = NOFOOD;
			}
		}
		return false;
	}

	/**
	 * Pheromonspur wird aktualisiert(berechnet)
	 */
	private void validatePheromoneTrail(){
		pTrail.validate( position, this.food );
	}
	
	/**
	 * die Ameise malt sich entsprechend ihres Futterzustandes auf das PufferImage der AntSimulation
	 */
	public void draw( Graphics g ){
		Color tmp;
		if( food == GOTFOOD )
			tmp = Color.red;
		else
			tmp = Color.black;
//		g.setColor(tmp);
//		g.fillRect( position.x, position.y, 1, 1);
		antSim.setUnderlayerColor(position.x, position.y, tmp.getRGB());
	}

	/**
	 * malt die Pheromonspur
	 */
	public void drawTrail( Graphics g ){
		pTrail.draw( g );
	}
	
	/**
	 * macht einen Schritt wenn moeglich
	 */
	public void stepForward(){
		
		Point nextPoint = new Point( position );
		
		if( this.stepDirection( nextPoint ) ){
			
			angleWay = 0; //bei einem neuen Schritt soll der Abprallwinkel neu berechnet werden
			
			antSim.getForestground()[position.x][position.y] *= -1;
			antSim.getForestground()[nextPoint.x][nextPoint.y] *= -1;
			
			position = nextPoint;
			
			validatePheromoneTrail();
		
		}
	}
	
}
